﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Media;
using Microsoft.Xna.Framework.Input;

namespace TakePhotoTogether.ImageProcessing
{
    /// <summary>
    /// Class that wrappers WriteableBitmap in order to handle pixel operations 
    /// using an easy and handful interface
    /// </summary>
    public class CVImage
    {
        /// <summary>
        /// Number of columns of the matrix.
        /// </summary>
        public int Width { get; private set; }

        /// <summary>
        /// Number of rows of the matrix.
        /// </summary>
        public int Height { get; private set; }

        /// <summary>
        /// Number of elements of the matrix.
        /// </summary>
        public int Length { get { return Pixels.Length; } }

        /// <summary>
        /// Matrix of pixels in array form
        /// </summary>
        public int[] Pixels { get; private set; }

        private WriteableBitmap bitmap = null;

        private readonly MediaLibrary library = new MediaLibrary();

        private string lastSavedName = "";

        public string LastSavedName
        {
            get { return lastSavedName; }
        }

        public WriteableBitmap Bitmap
        {
            get { return bitmap; }
            set { bitmap = value; }
        }

        /// <summary>
        /// Must be called from UI thread
        /// </summary>
        /// <param name="photoName"></param>
        /// <returns></returns>
        public static CVImage LoadCVImage(string photoName, CVImage loadedImage = null)
        {
            //Debug.WriteLine("CVImage::LoadCVImage");

            MediaLibrary library = new MediaLibrary();

            Picture picture = null;

            PictureAlbum rootAlbum = library.RootPictureAlbum;

            foreach (PictureAlbum album in rootAlbum.Albums)
            {
                if (album.Name == "Camera Roll")
                {
                    for (int p = 0; p < album.Pictures.Count; p++)
                    {
                        picture = album.Pictures[p];

                        if (picture.Name.Contains(photoName))
                        {
                            break;
                        }
                    }
                }
            }

            if (picture == null)
            {
                Debug.WriteLine("CVImage::LoadImage - Error when loading image - could not be found");
                return null;
            }


            try
            {
                if (loadedImage == null)
                {
                    BitmapImage image = new BitmapImage();
                    image.SetSource(picture.GetImage());

                    WriteableBitmap bitmap = new WriteableBitmap(image.PixelWidth, image.PixelHeight);
                    bitmap.SetSource(picture.GetImage());

                    loadedImage = new CVImage(bitmap.PixelWidth, bitmap.PixelHeight, bitmap);
                }
                else
                {
                    loadedImage.SetSource(picture.GetImage());
                }
            }
            catch(IndexOutOfRangeException e)
            {
                Debug.WriteLine("CVImage =>" + e.Message);
                loadedImage = null;
            }

            return loadedImage;
        }

        public CVImage(int width, int height) 
        {
            Width = width;
            Height = height;

            Application.Current.RootVisual.Dispatcher.BeginInvoke(() =>
                {
                    bitmap = new WriteableBitmap(width, height);
                    Pixels = bitmap.Pixels;
                });
        }

        public CVImage(int width, int height, WriteableBitmap bitmapImage)
        {
            Width = width;
            Height = height;

            bitmap = bitmapImage;
            Pixels = bitmap.Pixels;
        }

        public CVImage(int width, int height, byte[] pixels)
        {
            Width = width;
            Height = height;

            Application.Current.RootVisual.Dispatcher.BeginInvoke(() =>
                {
                    using (MemoryStream partnerStream = new MemoryStream(pixels))
                    {
                        bitmap = new WriteableBitmap(width, height);
                        bitmap.LoadJpeg(partnerStream);
                    }
                });

            Pixels = bitmap.Pixels;
        }

        public CVImage SetSource(Stream stream)
        {
            bitmap.SetSource(stream);

            Pixels = bitmap.Pixels;

            return this;
        }

        public CVImage LoadJpeg(byte[] imageData)
        {
            using (MemoryStream partnerStream = new MemoryStream(imageData))
            {
                Bitmap.LoadJpeg(partnerStream);
            }

            Pixels = Bitmap.Pixels;

            return this;
        }

        /// <summary>
        /// Creates a new resized WriteableBitmap.
        /// Code imported from WriteableBitmapEx codeplex project [Microsoft Public License]
        /// link: http://writeablebitmapex.codeplex.com/SourceControl/latest#tags/WBX_0.9.7.0/Source/WriteableBitmapEx/WriteableBitmapTransformationExtensions.cs
        /// </summary>
        /// <param name="bmp">The WriteableBitmap.</param>
        /// <param name="width">The new desired width.</param>
        /// <param name="height">The new desired height.</param>
        /// <param name="interpolation">The interpolation method that should be used.</param>
        /// <returns>A new WriteableBitmap that is a resized version of the input.</returns>
        public CVImage Resize(int newWidth, int newHeight, Interpolation interpolation = Interpolation.Bilinear, CVImage imageResult = null)
        {
            if (imageResult == null)
            {
                imageResult = new CVImage(newWidth, newHeight);
            }

            var result = imageResult.Bitmap;
            var pd = PixelUtil.Resize(Width, Height, Pixels, newWidth, newHeight, result.Pixels, interpolation);
            
            return imageResult;
        }

        /// <summary>
        /// Concatenate cropped images.
        /// Calling instance will be placed at right and the receiving instance
        /// will be placed at left
        /// </summary>
        /// <param name="image">image instance to be cropped and placed at left</param>
        /// <returns>Concatenated image</returns>
        public CVImage ConcatHalfImages(CVImage image, CVImage resultImage = null)
        {
            Debug.Assert(image.Height == Height, "dimensions of images should be equal");

            int halfSize = 398;

            int[] leftImage = PixelUtil.Crop(Width, Height, Pixels, halfSize, Height, (int)((Width - halfSize) * 0.5), 0);

            int[] rightImage = image.Bitmap.Pixels;

            // TODO
            //if (Height < image.Height)
            //{
            //    factor = Height * 1.0f / image.Height;
            //    image = image.Resize((int)(factor * image.Width), Height);
            //}
            //else if (Height > image.Height)
            //{
            //    factor = image.Height * 1.0f / Height;
            //    reducedImage = Resize((int)(factor * Width), image.Height);
            //}

            PixelUtil.Concat(halfSize, Height, leftImage, halfSize, Height, rightImage, resultImage.Pixels, 4);

            leftImage = null;
            rightImage = null;

            return resultImage;
        }
    

        public CVImage Concat(CVImage image, CVImage newImage = null)
        {
            //Debug.WriteLine("CVImage::Concat - my image size: (" + Width + " , " + Height + " )" );
            //Debug.WriteLine("CVImage::Concat - other image size: (" + image.Width + " , " + image.Height + " )");

            int finalWidth = Width + image.Width;
            int finalHeight = Height;

            if (Height < image.Height)
            {
                finalHeight = image.Height;
            }

            if (newImage == null && newImage.Width == finalWidth && newImage.Height == finalHeight)
            {
                newImage = new CVImage(finalWidth, finalHeight);
            }

            PixelUtil.Concat(Height, Width, Pixels, image.Height, image.Width, image.Pixels, newImage.Pixels);

            return newImage;
        }

        public byte[] ToBytes()
        {
            byte[] imageBytes = null;

            using (MemoryStream stream = new MemoryStream())
            {
                using (BinaryReader reader = new BinaryReader(stream))
                {
                    Bitmap.SaveJpeg(stream, Width, Height, 0, 100);
                    stream.Seek(0, SeekOrigin.Begin);

                    imageBytes = reader.ReadBytes((int)stream.Length);
                }
            }

            return imageBytes;
        }

        public CVImage SavePhoto(string fotoname = "", int imageQuality = 100)
        {
            if (String.IsNullOrEmpty(fotoname))
            {
                DateTime now = DateTime.Now;

                fotoname = string.Format("tap_and_shoot_together_{0}_{1}", now.ToString("yyyy_MM_dd"), now.Ticks % 1000000000);
            }

            lastSavedName = fotoname;

            using (MemoryStream finalStream = new MemoryStream())
            {
                Bitmap.SaveJpeg(finalStream, Width, Height, 0, imageQuality);
                finalStream.Seek(0, SeekOrigin.Begin);
                library.SavePictureToCameraRoll(fotoname, finalStream);
            }

            return this;
        }
    }
}
